/*******************************************************************************
 * Venice Noise Android Application
 * Copyright (C) 2011  Worcester Polytechnic Institute, Wesley Ripley
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * If you plan to use or modify any of this code please contact Fabio Carrera (carrera.fabio@gmail.com)
 * Also please attribute any code used in the final product to the developers. 
 * 
 * Author: Wesley Ripley (wripley@wpi.edu) 
 *******************************************************************************/
package org.venicenoise.app;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreProtocolPNames;
import org.venicenoise.app.location.LocationService;

import android.app.ProgressDialog;
import android.content.Context;
import android.location.Location;
import android.os.AsyncTask;
import android.util.Log;

/**
 * A AsyncTask that submits a SubmissionQue to the web with progress dialog
 * @author Wesley Ripley
 * @version 12/15/2011
 */
public class SubmitData extends AsyncTask<SubmissionQue, Integer, Integer> {
	/**
	 * The website to submit to
	 */
	private final static String website = "http://www.venicenoise.org/submit.php";
	
	private final static String OK_STATUS_LINE = "HTTP/1.1 200 OK";
	/**
	 * The response from the server that means it has received the data
	 * and dealt with it together
	 */
	private final static String OK_RESPONSE = "SUBMISSION OK\n";
	
	private final Context context;
	private ProgressDialog progressDialog;
	private boolean latestOnly = false;
	
	/**
	 * A interface to define what should happen after the data has all been submitted
	 * @author Wesley Ripley
	 * @version 12/15/2011
	 */
	public interface PostExecutable {
		/**
		 * Called when all submitting has finished
		 */
		public void onPostExecute();

	}
	
	private final PostExecutable postExecuter;
	
	/**
	 * Constructor for SubmitData
	 * @param context
	 * 		The context of the application
	 * @param postExecuter
	 * 		A PostExectutable defining what should happen when submitting finished
	 * @param latestOnly
	 * 		Whether the entire queue should be submitted or just the latest item.
	 * 		true means submit only the most recently queued, false to submit the entire queue
	 */
	public SubmitData(final Context context, final PostExecutable postExecuter, final boolean latestOnly)
	{
		this.context = context;
		this.postExecuter = postExecuter;
		this.latestOnly = latestOnly;
	}
	
	/**
	 * Constructor for SubmitData
	 * @param context
	 * 		The context of the application
	 * @param postExecuter
	 * 		A PostExectutable defining what should happen when submitting finished
	 */
	public SubmitData(final Context context, final PostExecutable postExecuter)
	{
		this(context,postExecuter,false);
	}
	
	/**
	 * Constructor for SubmitData
	 * @param context
	 * 		The context of the application
	 */
	public SubmitData(final Context context)
	{
		this(context,null,false);
	}
	
	/**
	 * Constructor for SubmitData
	 * @param context
	 * 		The context of the application
	 * @param latestOnly
	 */
	public SubmitData(final Context context, final boolean latestOnly)
	{
		this(context,null,latestOnly);
	}
	
	/** invoked on the UI thread immediately before the submission starts in the background */
	@Override
	protected void onPreExecute()
	{
		//show the progres dialog
		progressDialog = makeProgressDialog();
		progressDialog.show();
	}

	private ProgressDialog makeProgressDialog()
	{
		ProgressDialog progressDialog =  new ProgressDialog(context);;
		progressDialog.setMessage(context.getString(R.string.submitting));
		progressDialog.setProgressStyle(latestOnly ? ProgressDialog.STYLE_SPINNER : ProgressDialog.STYLE_HORIZONTAL);
		progressDialog.setCancelable(false);
		return progressDialog;
	}
	
	/**
	 * Submit the given SubmissionQue to the web in the background
	 * @param ques
	 * 		The SubmissionQues to submit. Should only be one queue.
	 * @return
	 * 		0 if successful, and error code otherwise.
	 */
	public Integer doInBackground(final SubmissionQue... ques)
	{
		for(SubmissionQue que : ques)
		{
			//get map of what needs to be submitted
			Map<Integer,NoiseData> submissions = latestOnly ? que.getLastSubmittedAsMap() : que.getAll();
			//reset progress of progress bar
			progressDialog.setProgress(0);
			progressDialog.setMax(submissions.keySet().size());
			//go through each submission
			for(int key : submissions.keySet())
			{
				NoiseData noiseData = submissions.get(key);
				int errorCode = submitData(noiseData);
				if(errorCode != 0)
				{
					//if there was an DO NOT clear from queue and exit immediately
					return errorCode;
				}
				//clear submission from queue
				que.deleteSubmission(key);
				if(AppUtil.DELETE_FILES)
				{
					File audioFile = noiseData.getAudioFile();
					if(audioFile != null)
					{
						audioFile.delete();
					}
					File photoFile = noiseData.getPhotoFile();
					if(photoFile != null)
					{
						photoFile.delete();
					}
				}
				progressDialog.incrementProgressBy(1);
			}
		}
		return 0;
	}
	/**
	 * Submit a single piece of data to the web
	 * @param noiseData
	 * 		The data to submit
	 * @return
	 * 		0 if successful, an error code otherwise
	 */
	private int submitData(NoiseData noiseData)
	{
		LocationService locationService = noiseData.getLocationService();
		if(!locationService.locationAvailable())
		{
			return -1;
		}
		Location location = locationService.getLocation();
		
		MultipartEntity entity = new MultipartEntity();
		try{
			//add each part to entity that will be submitted
			entity.addPart("type",new StringBody(String.valueOf(noiseData.getType().getValue())));
			entity.addPart("latitude",new StringBody(String.valueOf(location.getLatitude())));
			entity.addPart("longitude",new StringBody(String.valueOf(location.getLongitude())));
			entity.addPart("accuracy",new StringBody(String.valueOf(location.getAccuracy())));
			entity.addPart("bearing",new StringBody(String.valueOf(location.getBearing())));
			entity.addPart("spl",new StringBody(String.valueOf(noiseData.getSPL())));
			entity.addPart("leq",new StringBody(String.valueOf(noiseData.getLeq())));
			entity.addPart("sel",new StringBody(String.valueOf(noiseData.getSEL())));
			entity.addPart("description",new StringBody(noiseData.getDescription()));
			entity.addPart("timestamp",new StringBody(String.valueOf(noiseData.getTimestamp())));
			if(noiseData.getAudioFile() != null)
			{
				entity.addPart("audiofile",new FileBody(noiseData.getAudioFile()));
			}
			if(noiseData.getPhotoFile() != null)
			{
				if(AppUtil.LOGGING)
				{
					Log.i(AppUtil.LOG_TAG,"photoSubmitted: "+noiseData.getPhotoFile().getAbsolutePath());
				}
				entity.addPart("photofile",new FileBody(noiseData.getPhotoFile()));
			}
		}catch(UnsupportedEncodingException e)
		{
			Log.e(AppUtil.LOG_TAG,"UnsupposrtedEncodingException trying to make MultipartEntity");
			return -3;
		}
		return postToDatabase(entity);
	}
	/**
	 * Post fields to the Internet
	 * @param fields
	 * 		The fields to post
	 * @return
	 * 		0 if successful, an error code otherwise
	 */
	private int postToDatabase(final MultipartEntity fields)
	{
		//http post
		InputStream is;
		String statusLine;
		try{
			HttpClient httpclient = new DefaultHttpClient();
			httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, AppUtil.getUserAgentString(context));
			HttpPost httppost = new HttpPost(website);
			httppost.setEntity(fields);
			HttpResponse response = httpclient.execute(httppost);
			HttpEntity entity = response.getEntity();
			is = entity.getContent();
			statusLine = response.getStatusLine().toString();
			if(AppUtil.LOGGING)
			{
				Log.i(AppUtil.LOG_TAG, statusLine);
			}
		}
		catch(Exception e)
		{
			if(AppUtil.LOGGING)
			{
				Log.e(AppUtil.LOG_TAG, "Error in http connection "+e.toString());
			}
			return -2;
		}
		try{
	        String result = readResponse(is);
	        if(AppUtil.LOGGING)
	        {
	        	Log.i(AppUtil.LOG_TAG,result);
	        }
	        if(statusLine.equals(OK_STATUS_LINE) && result.equals(OK_RESPONSE))
	        {
	        	return 0;
	        }
	        else
	        {
	        	return -3;
	        }
		}catch(Exception e)
		{
			if(AppUtil.LOGGING)
			{
				Log.e(AppUtil.LOG_TAG, "Error reading server response: " + e.toString());
			}
			return -4;
		}
	}
	
	/**
	 * Read the response received from the server
	 * @param is
	 * 		The input stream to read from
	 * @return
	 * 		The response
	 * @throws IOException
	 * 		if there was an error reading the stream
	 */
	private String readResponse(InputStream is) throws IOException
	{
		BufferedReader reader = new BufferedReader(new InputStreamReader(is,"iso-8859-1"),8);
        StringBuilder sb = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) 
        {
        	sb.append(line + "\n");
        }
        is.close();
        return sb.toString();
	}
	
	/**
	 * Called when submission in background has finished
	 */
	@Override
    protected void onPostExecute(final Integer errorCode) {
		//dismiss the dialog
        if (progressDialog.isShowing()) {
        	progressDialog.dismiss();
        }
        if(errorCode != 0)
        {
        	//inform user of error
        	AppUtil.showErrorDialog(context, submitDataErrorMessage(errorCode));
        }
        else if(postExecuter != null)
        {
        	postExecuter.onPostExecute();
        }
        
    }
	
	/**
	 * Converts the error code from a submit data thread into a error message
	 * @param code
	 * 		the error code from the thread
	 * @return
	 * 		the appropriate error message, null if there was no error
	 */
	private String submitDataErrorMessage(int code)
	{
		//TODO: These error codes really should be constants at the top, not sure why I didn't do that in the first place.
		int errorStringId;
		switch(code)
		{
			case 0:
				return null;
			case -1:
				errorStringId = R.string.noLocation;
				break;
			case -2:
				errorStringId = R.string.noInternet;
				break;
			default:
				errorStringId = R.string.cannotSubmit;
				break;
		}
		return context.getString(errorStringId);
	}

}
